/*
 * Copyright (C) 2018
 *
 * Brick Yang <printfxxx@163.com>
 *
 * This program is free software. You can redistribute it and/or
 * modify it as you like.
 */

/**
 * @file	pmu_arm64.c
 * @brief	Performance monitor unit module for arm64
 */

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/percpu.h>
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>

#include <asm/sysreg.h>
#include <asm/uaccess.h>

#define ARMV8_PMU_PMCR_E	(1 << 0)
#define ARMV8_PMU_PMCR_P	(1 << 1)
#define ARMV8_PMU_PMCR_C	(1 << 2)
#define ARMV8_PMU_PMCR_D	(1 << 3)
#define ARMV8_PMU_PMCR_X	(1 << 4)
#define ARMV8_PMU_PMCR_DP	(1 << 5)
#define ARMV8_PMU_PMCR_LC	(1 << 6)
#define ARMV8_PMU_PMCR_N_SHIFT	11
#define ARMV8_PMU_PMCR_N_MASK	0x1f

#define ARMV8_PMU_USERENR_EN	(1 << 0)

#define ARMV8_PMU_CCNT_SEL_BIT	31

#ifndef read_sysreg
#define read_sysreg(r) ({					\
	u64 __val;						\
	asm volatile("mrs %0, " __stringify(r) : "=r" (__val)); \
	__val;							\
})
#endif

#ifndef write_sysreg
#define write_sysreg(v, r) do {					\
	u64 __val = (u64)v;					\
	asm volatile("msr " __stringify(r) ", %x0"		\
			: : "rZ" (__val));			\
} while (0)
#endif

typedef struct pmu_cpu {
	unsigned int cnt_nr;
} pmu_cpu_t;

typedef union pmu_cpu_call {
	u32 u32[2];
	u64 u64;
} pmu_cpu_call_t;

static DEFINE_PER_CPU(pmu_cpu_t, all_pmu);

static unsigned int pmu_cpu_curr, pmu_evcnt_sel_curr;
static struct proc_dir_entry *pde_pmu, *pde_pmu_cpu, *pde_pmu_info, *pde_pmu_evcnt_sel,
			     *pde_pmu_evcnt_type, *pde_pmu_evcnt, *pde_pmu_evcnt_en, *pde_pmu_ccnt,
			     *pde_pmu_ccnt_en, *pde_pmu_reset;

static int pmu_cpu_show(struct seq_file *s, void *v)
{
	seq_printf(s, "%u\n", pmu_cpu_curr);

	return 0;
}

static int pmu_cpu_open(struct inode *inode, struct file *file)
{
	return single_open(file, pmu_cpu_show, NULL);
}

static ssize_t pmu_cpu_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
	int rc;
	char str[64];
	unsigned int cpu;

	if (copy_from_user(str, buf, sizeof(str))) {
		rc = -EFAULT;
		goto err;
	}
	if (count < sizeof(str)) {
		str[count] = '\0';
	} else {
		str[sizeof(str) - 1] = '\0';
	}
	cpu = simple_strtoul(str, NULL, 0);
	if (cpu_is_offline(cpu)) {
		rc = -EINVAL;
		goto err;
	}
	pmu_cpu_curr = cpu;

	return count;
err:
	return rc;
}

static int pmu_info_show(struct seq_file *s, void *v)
{
	pmu_cpu_t *pmu;

	pmu = per_cpu_ptr(&all_pmu, pmu_cpu_curr);
	seq_printf(s, "CPU number: %u\n", pmu_cpu_curr);
	seq_printf(s, "Event counter number: %u\n", pmu->cnt_nr);

	return 0;
}

static int pmu_info_open(struct inode *inode, struct file *file)
{
	return single_open(file, pmu_info_show, NULL);
}

static int pmu_evcnt_sel_show(struct seq_file *s, void *v)
{
	seq_printf(s, "%u\n", pmu_evcnt_sel_curr);

	return 0;
}

static int pmu_evcnt_sel_open(struct inode *inode, struct file *file)
{
	return single_open(file, pmu_evcnt_sel_show, NULL);
}

static ssize_t pmu_evcnt_sel_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
	int rc;
	char str[64];
	pmu_cpu_t *pmu;
	unsigned int sel;

	pmu = per_cpu_ptr(&all_pmu, pmu_cpu_curr);

	if (copy_from_user(str, buf, sizeof(str))) {
		rc = -EFAULT;
		goto err;
	}
	if (count < sizeof(str)) {
		str[count] = '\0';
	} else {
		str[sizeof(str) - 1] = '\0';
	}
	sel = simple_strtoul(str, NULL, 0);
	if (sel >= pmu->cnt_nr) {
		rc = -EINVAL;
		goto err;
	}
	pmu_evcnt_sel_curr = sel;

	return count;
err:
	return rc;
}

static void pmu_evcnt_type_cpu_read(void *arg)
{
	unsigned int sel;
	pmu_cpu_call_t *call = arg;

	sel = call->u32[0];
	write_sysreg(sel, pmselr_el0);
	isb();
	call->u32[1] = read_sysreg(pmxevtyper_el0);
}

static int pmu_evcnt_type_show(struct seq_file *s, void *v)
{
	pmu_cpu_call_t call;

	call.u32[0] = pmu_evcnt_sel_curr;
	smp_call_function_single(pmu_cpu_curr, pmu_evcnt_type_cpu_read, (void *)&call, true);
	seq_printf(s, "0x%08x\n", call.u32[1]);

	return 0;
	return 0;
}

static int pmu_evcnt_type_open(struct inode *inode, struct file *file)
{
	return single_open(file, pmu_evcnt_type_show, NULL);
}

static void pmu_evcnt_type_cpu_write(void *arg)
{
	bool en;
	unsigned int sel, evt;
	pmu_cpu_call_t *call = arg;

	sel = call->u32[0];
	evt = call->u32[1];
	en = !!(read_sysreg(pmcntenset_el0) & BIT(sel));
	if (en) {
		write_sysreg(BIT(sel), pmcntenclr_el0);
	}
	write_sysreg(sel, pmselr_el0);
	isb();
	write_sysreg(evt, pmxevtyper_el0);
	if (en) {
		write_sysreg(BIT(sel), pmcntenset_el0);
	}
}

static ssize_t pmu_evcnt_type_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
	int rc;
	char str[64];
	pmu_cpu_call_t call;

	if (copy_from_user(str, buf, sizeof(str))) {
		rc = -EFAULT;
		goto err;
	}
	if (count < sizeof(str)) {
		str[count] = '\0';
	} else {
		str[sizeof(str) - 1] = '\0';
	}
	call.u32[0] = pmu_evcnt_sel_curr;
	call.u32[1] = simple_strtoul(str, NULL, 0);
	smp_call_function_single(pmu_cpu_curr, pmu_evcnt_type_cpu_write, (void *)&call, true);

	return count;
err:
	return rc;
}

static void pmu_evcnt_cpu_read(void *arg)
{
	unsigned int sel;
	pmu_cpu_call_t *call = arg;

	sel = call->u32[0];
	write_sysreg(sel, pmselr_el0);
	isb();
	call->u32[1] = read_sysreg(pmxevcntr_el0);
}

static int pmu_evcnt_show(struct seq_file *s, void *v)
{
	pmu_cpu_call_t call;

	call.u32[0] = pmu_evcnt_sel_curr;
	smp_call_function_single(pmu_cpu_curr, pmu_evcnt_cpu_read, (void *)&call, true);
	seq_printf(s, "0x%08x\n", call.u32[1]);

	return 0;
}

static int pmu_evcnt_open(struct inode *inode, struct file *file)
{
	return single_open(file, pmu_evcnt_show, NULL);
}

static void pmu_evcnt_cpu_write(void *arg)
{
	uint32_t cnt;
	unsigned int sel;
	pmu_cpu_call_t *call = arg;

	sel = call->u32[0];
	cnt = call->u32[1];
	write_sysreg(sel, pmselr_el0);
	isb();
	write_sysreg(cnt, pmxevcntr_el0);
}

static ssize_t pmu_evcnt_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
	int rc;
	char str[64];
	pmu_cpu_call_t call;

	if (copy_from_user(str, buf, sizeof(str))) {
		rc = -EFAULT;
		goto err;
	}
	if (count < sizeof(str)) {
		str[count] = '\0';
	} else {
		str[sizeof(str) - 1] = '\0';
	}
	call.u32[0] = pmu_evcnt_sel_curr;
	call.u32[1] = simple_strtoul(str, NULL, 0);
	smp_call_function_single(pmu_cpu_curr, pmu_evcnt_cpu_write, (void *)&call, true);

	return count;
err:
	return rc;
}

static void pmu_evcnt_en_cpu_read(void *arg)
{
	unsigned int sel;
	pmu_cpu_call_t *call = arg;

	sel = call->u32[0];
	write_sysreg(sel, pmselr_el0);
	isb();
	call->u32[1] = !!(read_sysreg(pmcntenset_el0) & BIT(sel));
}

static int pmu_evcnt_en_cpu_show(struct seq_file *s, void *v)
{
	pmu_cpu_call_t call;

	call.u32[0] = pmu_evcnt_sel_curr;
	smp_call_function_single(pmu_cpu_curr, pmu_evcnt_en_cpu_read, (void *)&call, true);
	seq_printf(s, "%u\n", call.u32[1]);

	return 0;
}

static int pmu_evcnt_en_open(struct inode *inode, struct file *file)
{
	return single_open(file, pmu_evcnt_en_cpu_show, NULL);
}

static void pmu_evcnt_en_cpu_write(void *arg)
{
	unsigned int sel;
	pmu_cpu_call_t *call = arg;

	sel = call->u32[0];
	if (call->u32[1]) {
		write_sysreg(BIT(sel), pmcntenset_el0);
	} else {
		write_sysreg(BIT(sel), pmcntenclr_el0);
	}
}

static ssize_t pmu_evcnt_en_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
	int rc;
	char str[64];
	pmu_cpu_call_t call;

	if (copy_from_user(str, buf, sizeof(str))) {
		rc = -EFAULT;
		goto err;
	}
	if (count < sizeof(str)) {
		str[count] = '\0';
	} else {
		str[sizeof(str) - 1] = '\0';
	}
	call.u32[0] = pmu_evcnt_sel_curr;
	call.u32[1] = !!simple_strtoul(str, NULL, 0);
	smp_call_function_single(pmu_cpu_curr, pmu_evcnt_en_cpu_write, (void *)&call, true);

	return count;
err:
	return rc;
}

static void pmu_ccnt_cpu_read(void *arg)
{
	pmu_cpu_call_t *call = arg;

	call->u64 = read_sysreg(pmccntr_el0);
}

static int pmu_ccnt_show(struct seq_file *s, void *v)
{
	pmu_cpu_call_t call;

	smp_call_function_single(pmu_cpu_curr, pmu_ccnt_cpu_read, (void *)&call, true);
	seq_printf(s, "0x%016llx\n", call.u64);

	return 0;
}

static int pmu_ccnt_open(struct inode *inode, struct file *file)
{
	return single_open(file, pmu_ccnt_show, NULL);
}

static void pmu_ccnt_cpu_write(void *arg)
{
	pmu_cpu_call_t *call = arg;

	write_sysreg(call->u64, pmccntr_el0);
}

static ssize_t pmu_ccnt_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
	int rc;
	char str[64];
	pmu_cpu_call_t call;

	if (copy_from_user(str, buf, sizeof(str))) {
		rc = -EFAULT;
		goto err;
	}
	if (count < sizeof(str)) {
		str[count] = '\0';
	} else {
		str[sizeof(str) - 1] = '\0';
	}
	call.u64 = simple_strtoull(str, NULL, 0);
	smp_call_function_single(pmu_cpu_curr, pmu_ccnt_cpu_write, (void *)&call, true);

	return count;
err:
	return rc;
}

static void pmu_ccnt_en_cpu_read(void *arg)
{
	pmu_cpu_call_t *call = arg;

	call->u32[0] = !!(read_sysreg(pmcntenset_el0) & BIT(ARMV8_PMU_CCNT_SEL_BIT));
}

static int pmu_ccnt_en_cpu_show(struct seq_file *s, void *v)
{
	pmu_cpu_call_t call;

	call.u32[0] = pmu_evcnt_sel_curr;
	smp_call_function_single(pmu_cpu_curr, pmu_ccnt_en_cpu_read, (void *)&call, true);
	seq_printf(s, "%u\n", call.u32[0]);

	return 0;
}

static int pmu_ccnt_en_open(struct inode *inode, struct file *file)
{
	return single_open(file, pmu_ccnt_en_cpu_show, NULL);
}

static void pmu_ccnt_en_cpu_write(void *arg)
{
	pmu_cpu_call_t *call = arg;

	if (call->u32[0]) {
		write_sysreg(BIT(ARMV8_PMU_CCNT_SEL_BIT), pmcntenset_el0);
	} else {
		write_sysreg(BIT(ARMV8_PMU_CCNT_SEL_BIT), pmcntenclr_el0);
	}
}

static ssize_t pmu_ccnt_en_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
	int rc;
	char str[64];
	pmu_cpu_call_t call;

	if (copy_from_user(str, buf, sizeof(str))) {
		rc = -EFAULT;
		goto err;
	}
	if (count < sizeof(str)) {
		str[count] = '\0';
	} else {
		str[sizeof(str) - 1] = '\0';
	}
	call.u32[0] = !!simple_strtoul(str, NULL, 0);
	smp_call_function_single(pmu_cpu_curr, pmu_ccnt_en_cpu_write, (void *)&call, true);

	return count;
err:
	return rc;
}

static void pmu_reset_cpu_write(void *arg)
{
	pmu_cpu_t *pmu;
	unsigned int i;

	pmu = this_cpu_ptr(&all_pmu);

	for (i = 0; i < pmu->cnt_nr; i++) {
		write_sysreg(BIT(i), pmcntenclr_el0);
		write_sysreg(BIT(i), pmintenclr_el1);
		isb();
		write_sysreg(BIT(i), pmovsclr_el0);
		isb();
		write_sysreg(i, pmselr_el0);
		isb();
		write_sysreg(0, pmxevcntr_el0);
	}

	write_sysreg(BIT(ARMV8_PMU_CCNT_SEL_BIT), pmcntenclr_el0);
	write_sysreg(BIT(ARMV8_PMU_CCNT_SEL_BIT), pmintenclr_el1);
	write_sysreg(0, pmccntr_el0);
}

static ssize_t pmu_reset_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
	int rc;
	char str[64];
	unsigned int cpu;

	if (copy_from_user(str, buf, sizeof(str))) {
		rc = -EFAULT;
		goto err;
	}
	if (count < sizeof(str)) {
		str[count] = '\0';
	} else {
		str[sizeof(str) - 1] = '\0';
	}
	if (simple_strtoul(str, NULL, 0)) {
		for_each_online_cpu(cpu) {
			smp_call_function_single(cpu, pmu_reset_cpu_write, NULL, true);
		}
		pmu_cpu_curr = 0;
		pmu_evcnt_sel_curr = 0;
	}

	return count;
err:
	return rc;
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0)
static const struct file_operations pmu_cpu_fops = {
	.owner	 = THIS_MODULE,
	.open	 = pmu_cpu_open,
	.read	 = seq_read,
	.write	 = pmu_cpu_write,
	.llseek	 = seq_lseek,
	.release = single_release,
};

static const struct file_operations pmu_info_fops = {
	.owner	 = THIS_MODULE,
	.open	 = pmu_info_open,
	.read	 = seq_read,
	.llseek	 = seq_lseek,
	.release = single_release,
};

static const struct file_operations pmu_evcnt_sel_fops = {
	.owner	 = THIS_MODULE,
	.open	 = pmu_evcnt_sel_open,
	.read	 = seq_read,
	.write	 = pmu_evcnt_sel_write,
	.llseek	 = seq_lseek,
	.release = single_release,
};

static const struct file_operations pmu_evcnt_type_fops = {
	.owner	 = THIS_MODULE,
	.open	 = pmu_evcnt_type_open,
	.read	 = seq_read,
	.write	 = pmu_evcnt_type_write,
	.llseek	 = seq_lseek,
	.release = single_release,
};

static const struct file_operations pmu_evcnt_fops = {
	.owner	 = THIS_MODULE,
	.open	 = pmu_evcnt_open,
	.read	 = seq_read,
	.write	 = pmu_evcnt_write,
	.llseek	 = seq_lseek,
	.release = single_release,
};

static const struct file_operations pmu_evcnt_en_fops = {
	.owner	 = THIS_MODULE,
	.open	 = pmu_evcnt_en_open,
	.read	 = seq_read,
	.write	 = pmu_evcnt_en_write,
	.llseek	 = seq_lseek,
	.release = single_release,
};

static const struct file_operations pmu_ccnt_fops = {
	.owner	 = THIS_MODULE,
	.open	 = pmu_ccnt_open,
	.read	 = seq_read,
	.write	 = pmu_ccnt_write,
	.llseek	 = seq_lseek,
	.release = single_release,
};

static const struct file_operations pmu_ccnt_en_fops = {
	.owner	 = THIS_MODULE,
	.open	 = pmu_ccnt_en_open,
	.read	 = seq_read,
	.write	 = pmu_ccnt_en_write,
	.llseek	 = seq_lseek,
	.release = single_release,
};

static const struct file_operations pmu_reset_fops = {
	.owner	= THIS_MODULE,
	.write = pmu_reset_write,
};
#else
static const struct proc_ops pmu_cpu_fops = {
	.proc_open    = pmu_cpu_open,
	.proc_read    = seq_read,
	.proc_write   = pmu_cpu_write,
	.proc_lseek   = seq_lseek,
	.proc_release = single_release,
};

static const struct proc_ops pmu_info_fops = {
	.proc_open    = pmu_info_open,
	.proc_read    = seq_read,
	.proc_lseek   = seq_lseek,
	.proc_release = single_release,
};

static const struct proc_ops pmu_evcnt_sel_fops = {
	.proc_open    = pmu_evcnt_sel_open,
	.proc_read    = seq_read,
	.proc_write   = pmu_evcnt_sel_write,
	.proc_lseek   = seq_lseek,
	.proc_release = single_release,
};

static const struct proc_ops pmu_evcnt_type_fops = {
	.proc_open    = pmu_evcnt_type_open,
	.proc_read    = seq_read,
	.proc_write   = pmu_evcnt_type_write,
	.proc_lseek   = seq_lseek,
	.proc_release = single_release,
};

static const struct proc_ops pmu_evcnt_fops = {
	.proc_open    = pmu_evcnt_open,
	.proc_read    = seq_read,
	.proc_write   = pmu_evcnt_write,
	.proc_lseek   = seq_lseek,
	.proc_release = single_release,
};

static const struct proc_ops pmu_evcnt_en_fops = {
	.proc_open    = pmu_evcnt_en_open,
	.proc_read    = seq_read,
	.proc_write   = pmu_evcnt_en_write,
	.proc_lseek   = seq_lseek,
	.proc_release = single_release,
};

static const struct proc_ops pmu_ccnt_fops = {
	.proc_open    = pmu_ccnt_open,
	.proc_read    = seq_read,
	.proc_write   = pmu_ccnt_write,
	.proc_lseek   = seq_lseek,
	.proc_release = single_release,
};

static const struct proc_ops pmu_ccnt_en_fops = {
	.proc_open    = pmu_ccnt_en_open,
	.proc_read    = seq_read,
	.proc_write   = pmu_ccnt_en_write,
	.proc_lseek   = seq_lseek,
	.proc_release = single_release,
};

static const struct proc_ops pmu_reset_fops = {
	.proc_write = pmu_reset_write,
};
#endif

static void __init pmu_init_cpu(void *arg)
{
	uint64_t pmcr;
	pmu_cpu_t *pmu;

	pmu = this_cpu_ptr(&all_pmu);
	pmcr = read_sysreg(pmcr_el0);
	pmu->cnt_nr = (pmcr >> ARMV8_PMU_PMCR_N_SHIFT) & ARMV8_PMU_PMCR_N_MASK;
	write_sysreg(ARMV8_PMU_PMCR_LC | ARMV8_PMU_PMCR_C | ARMV8_PMU_PMCR_P | ARMV8_PMU_PMCR_E, pmcr_el0);
	write_sysreg(ARMV8_PMU_USERENR_EN, pmuserenr_el0);
	pmu_reset_cpu_write(NULL);

	pr_debug("CPU %u: %u counter available\n", smp_processor_id(), pmu->cnt_nr);
}

static int __init pmu_init(void)
{
	int rc;
	unsigned int cpu;

	if (!(pde_pmu = proc_mkdir("pmu", NULL))) {
		rc = -ENOMEM;
		pr_err("Failed to create dir /proc/pmu\n");
		goto err;
	}

	if (!(pde_pmu_cpu = proc_create("cpu", 0, pde_pmu, &pmu_cpu_fops))) {
		rc = -ENOMEM;
		pr_err("Failed to create file /proc/pmu/cpu\n");
		goto err_pde_cpu;
	}

	if (!(pde_pmu_info = proc_create("info", 0, pde_pmu, &pmu_info_fops))) {
		rc = -ENOMEM;
		pr_err("Failed to create file /proc/pmu/info\n");
		goto err_pde_info;
	}

	if (!(pde_pmu_evcnt_sel = proc_create("evcnt_sel", 0, pde_pmu, &pmu_evcnt_sel_fops))) {
		rc = -ENOMEM;
		pr_err("Failed to create file /proc/pmu/evcnt_sel\n");
		goto err_pde_evcnt_sel;
	}

	if (!(pde_pmu_evcnt_type = proc_create("evcnt_type", 0, pde_pmu, &pmu_evcnt_type_fops))) {
		rc = -ENOMEM;
		pr_err("Failed to create file /proc/pmu/evcnt_type\n");
		goto err_pde_evcnt_type;
	}

	if (!(pde_pmu_evcnt = proc_create("evcnt", 0, pde_pmu, &pmu_evcnt_fops))) {
		rc = -ENOMEM;
		pr_err("Failed to create file /proc/pmu/evcnt\n");
		goto err_pde_evcnt;
	}

	if (!(pde_pmu_evcnt_en = proc_create("evcnt_en", 0, pde_pmu, &pmu_evcnt_en_fops))) {
		rc = -ENOMEM;
		pr_err("Failed to create file /proc/pmu/evcnt_en\n");
		goto err_pde_en;
	}

	if (!(pde_pmu_ccnt = proc_create("ccnt", 0, pde_pmu, &pmu_ccnt_fops))) {
		rc = -ENOMEM;
		pr_err("Failed to create file /proc/pmu/ccnt\n");
		goto err_pde_ccnt;
	}

	if (!(pde_pmu_ccnt_en = proc_create("ccnt_en", 0, pde_pmu, &pmu_ccnt_en_fops))) {
		rc = -ENOMEM;
		pr_err("Failed to create file /proc/pmu/ccnt_en\n");
		goto err_pde_ccnt_en;
	}

	if (!(pde_pmu_reset = proc_create("reset", 0, pde_pmu, &pmu_reset_fops))) {
		rc = -ENOMEM;
		pr_err("Failed to create file /proc/pmu/reset\n");
		goto err_pde_reset;
	}

	for_each_online_cpu(cpu) {
		smp_call_function_single(cpu, pmu_init_cpu, NULL, true);
	}

	return 0;
err_pde_reset:
	proc_remove(pde_pmu_ccnt_en);
err_pde_ccnt_en:
	proc_remove(pde_pmu_ccnt);
err_pde_ccnt:
	proc_remove(pde_pmu_evcnt_en);
err_pde_en:
	proc_remove(pde_pmu_evcnt);
err_pde_evcnt:
	proc_remove(pde_pmu_evcnt_type);
err_pde_evcnt_type:
	proc_remove(pde_pmu_evcnt_sel);
err_pde_evcnt_sel:
	proc_remove(pde_pmu_info);
err_pde_info:
	proc_remove(pde_pmu_cpu);
err_pde_cpu:
	proc_remove(pde_pmu);
err:
	return rc;
}

static void __exit pmu_exit(void)
{
	proc_remove(pde_pmu_reset);
	proc_remove(pde_pmu_ccnt_en);
	proc_remove(pde_pmu_ccnt);
	proc_remove(pde_pmu_evcnt_en);
	proc_remove(pde_pmu_evcnt);
	proc_remove(pde_pmu_evcnt_type);
	proc_remove(pde_pmu_evcnt_sel);
	proc_remove(pde_pmu_info);
	proc_remove(pde_pmu_cpu);
	proc_remove(pde_pmu);
}

module_init(pmu_init);
module_exit(pmu_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("ARM64 PMU module");
